/**
 * Copyright 2021 xpstem.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.xpstem.lelink.operation;

import com.xpstem.lelink.enums.Color;
import com.xpstem.lelink.enums.InputPort;
import com.xpstem.lelink.enums.Layer;
import com.xpstem.lelink.enums.OutputPort;
import com.xpstem.lelink.type.Data16;
import com.xpstem.lelink.type.Data32;
import com.xpstem.lelink.type.Data8;

import java.io.IOException;
import java.io.OutputStream;

/**
 * Local constant encoder.
 * @author billy zhang
 */
public class LCEncoder {

    /**
     * long format, 1 byte follow
     */
    private static final short LC_BYTE1_PREFIX      = 0b1000_0001;
    /**
     * long format, 2 bytes follow
     */
    private static final short LC_BYTE2_PREFIX      = 0b1000_0010;
    /**
     * long format, 4 byte follow
     */
    private static final short LC_BYTE4_PREFIX      = 0b1000_0011;
    /**
     *zero terminated string
     */
    private static final short LC_STR_PREFIX        = 0b1000_0100;

    private static final int BYTE0_POSITIVE_MAX = 32;
    private static final int BYTE0_NEGATIVE_MAX = -32;

    private static final int BYTE1_POSITIVE_MAX = 128;
    private static final int BYTE1_NEGATIVE_MAX = -128;

    private static final int BYTE2_POSITIVE_MAX = 32768;
    private static final int BYTE2_NEGATIVE_MAX = -32768;

    private int intValue;

    private String strValue;

    private LCEncoder(int value) {
        this.intValue = value;
    }

    private LCEncoder(String value) {
        this.strValue = value;
    }

    public int getIntValue() {
        return intValue;
    }

    public String getStrValue() { return strValue; }

    public static LCEncoder from(int value) {
        return new LCEncoder(value);
    }

    public static LCEncoder from(String value) {
        return new LCEncoder(value);
    }

    public static LCEncoder from(Layer layer) {
        return new LCEncoder(layer.value());
    }

    public static LCEncoder from(InputPort inPort) {
        return new LCEncoder(inPort.value());
    }

    public static LCEncoder from(OutputPort... outPorts) {
        int val = 0;
        for (byte i=0; i<outPorts.length; i++) {
            val += outPorts[i].value();
        }
        return new LCEncoder(val);
    }

    public static LCEncoder from(Color color) {
        return new LCEncoder(color.value());
    }

    public static LCEncoder from(Data8 value) {
        return new LCEncoder(value.getValue());
    }

    public static LCEncoder from(Data16 value) {
        return new LCEncoder(value.getValue());
    }

    public static LCEncoder from(Data32 value) {
        return new LCEncoder(value.getValue());
    }

    public void lc0(OutputStream outStream) throws IOException {
        int encode = intValue >= 0 ?
                intValue & 0b0001_1111:
                ((-intValue & 0b0001_1111) + 0b0010_0000);
        outStream.write(encode);
    }

    public void lc1(OutputStream outStream) throws IOException {
        outStream.write(LC_BYTE1_PREFIX);
        outStream.write((byte) intValue);
    }

    public void lc2(OutputStream outStream) throws IOException {
        outStream.write(LC_BYTE2_PREFIX);
        outStream.write((byte) intValue);
        outStream.write((byte)(intValue >>8));
    }

    public void lc4(OutputStream outStream) throws IOException {
        outStream.write(LC_BYTE4_PREFIX);
        outStream.write((byte) intValue);
        outStream.write((byte)(intValue >>8));
        outStream.write((byte)(intValue >>16));
        outStream.write((byte)(intValue >>24));
    }

    public void lcs(OutputStream outStream) throws IOException {
        outStream.write(LC_STR_PREFIX );
        outStream.write( strValue.getBytes() );
        outStream.write( 0x00);
    }

    public void lcx(OutputStream outStream) throws IOException {
        if (intValue > BYTE0_NEGATIVE_MAX && intValue < BYTE0_POSITIVE_MAX) {
            lc0(outStream);
            return;
        }

        if (intValue > BYTE1_NEGATIVE_MAX && intValue < BYTE1_POSITIVE_MAX) {
            lc1(outStream);
            return;
        }

        if (intValue > BYTE2_NEGATIVE_MAX && intValue < BYTE2_POSITIVE_MAX) {
            lc2(outStream);
            return;
        }

        lc4(outStream);
    }
}
